Skip to content

Fix #765: Upgrade 17 decision models to optimization (Group A + B)#771

Merged
zazabap merged 26 commits intomainfrom
upgrade-decision-to-optimization-765
Mar 25, 2026
Merged

Fix #765: Upgrade 17 decision models to optimization (Group A + B)#771
zazabap merged 26 commits intomainfrom
upgrade-decision-to-optimization-765

Conversation

@isPANN
Copy link
Collaborator

@isPANN isPANN commented Mar 25, 2026

Summary

  • Upgrade all 17 decision models from Value = Or + threshold to true optimization (Value = Max<T> / Min<T>)
  • Rewrite 7 ILP rules from feasibility to optimization (add objective function, remove threshold constraint)
  • Redesign config space for 3 models with variable-length outputs (padding encoding)
  • Update CLI create commands, canonical examples, paper entries, and all tests

Group A — single-threshold models (12)

Model Value ILP Config redesign
LongestCircuit Max<W::Sum>
OptimalLinearArrangement Min<usize>
RuralPostman Min<W::Sum>
MixedChinesePostman Min<W::Sum>
StackerCrane Min<i32>
MinimumCardinalityKey Min<i64>
SequencingToMinimizeMaximumCumulativeCost Min<i64>
MultipleCopyFileAllocation Min<i64>
ExpectedRetrievalCost Min<f64>
SumOfSquaresPartition Min<i64>
LongestCommonSubsequence Max<usize> ✓ padding
ShortestCommonSupersequence Min<usize> ✓ padding

Group B — constrained-optimization models (5)

Model Keep (constraint) Optimize Value ILP
MinimumCutIntoBoundedSets size_bound cut weight Min<W::Sum>
ShortestWeightConstrainedPath weight_bound path length Min<N::Sum>
CapacityAssignment delay_budget cost Min<u128>
MinMaxMulticenter k (centers) max distance Min<W::Sum> ✓ minimax
LengthBoundedDisjointPaths max_length # paths Max<usize>

Test plan

  • make check (fmt + clippy + test) passes
  • cargo test --lib --features example-db — 3206 passed
  • cargo test -p problemreductions-cli — 17 passed
  • cargo test --test main (integration + doc tests) — all passed
  • cargo clippy --all-targets — 0 warnings

Closes #765

🤖 Generated with Claude Code

isPANN and others added 19 commits March 25, 2026 12:26
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Min<usize>)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…::Sum>)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…<i64>)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…to optimization (Min<i64>)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… (Min<i64>) + ILP rewrite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…<f64>) + ILP rewrite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…<i64>) + ILP rewrite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Max<usize>) + ILP rewrite + config redesign

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n (Min<usize>) + config redesign

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove bound parameter from 8 model constructor calls in CLI create
- Remove associated bound parsing/validation code and CLI tests
- Fix SumOfSquaresPartition canonical example (optimal is 226, not 230)
- Fix StackerCrane example-db test assertion (no longer has bound field)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…(Min<W::Sum>)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ion (Min<N::Sum>) + ILP rewrite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…28>) + ILP rewrite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Sum>) + minimax ILP rewrite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… (Max<usize>) + config redesign

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@isPANN isPANN changed the title Fix #765: Upgrade 12 Group A decision models to optimization Fix #765: Upgrade 17 decision models to optimization (Group A + B) Mar 25, 2026
isPANN and others added 2 commits March 25, 2026 14:42
…ptimization-765

# Conflicts:
#	docs/paper/reductions.typ
… merge

- Convert 10 ILP rules from feasibility to optimization (remove bound, add objective)
- Fix SCS ILP: add contiguous padding constraint, fix alphabet_size field
- Mark OLA ILP<i32> tests as ignored (ILP solver too slow for integer variables)
- Fix sequencing ILP test to use ILPSolver instead of brute-force on ILP<i32>

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@zazabap zazabap self-requested a review March 25, 2026 08:29
isPANN and others added 3 commits March 25, 2026 16:42
…nces

- model_specs_are_optimal now tries brute-force first for instances with
  search space ≤ 2^20, avoiding HiGHS hangs on ILP<i32> reductions
- Hardcode OLA and Sequencing ILP canonical example solutions (avoid
  calling ILPSolver at example-db build time for ILP<i32> targets)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: ILP<i32> variables default to domain [0, 2^31], causing HiGHS
to hang on even tiny instances. Two fixes:

1. ILP solver: derive tighter per-variable upper bounds from single-variable
   ≤ constraints before passing to HiGHS (covers x and p variables)
2. OLA ILP: add z_e ≤ n-1 bound (max position difference)
3. Sequencing ILP: add z ≤ Σ|costs| bound (max cumulative cost)

Restores ILPSolver calls in canonical rule examples (removes hardcoded
solutions) and reverts model_specs_are_optimal to ILP-first strategy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lver test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link

codecov bot commented Mar 25, 2026

Codecov Report

❌ Patch coverage is 99.25981% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.81%. Comparing base (88fb8f7) to head (cd58bdf).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/models/graph/rural_postman.rs 75.00% 4 Missing ⚠️
...c/models/graph/shortest_weight_constrained_path.rs 88.23% 2 Missing ⚠️
src/models/graph/mixed_chinese_postman.rs 94.11% 1 Missing ⚠️
src/models/misc/longest_common_subsequence.rs 96.66% 1 Missing ⚠️
src/solvers/customized/solver.rs 95.65% 1 Missing ⚠️
...t_tests/rules/shortestweightconstrainedpath_ilp.rs 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #771      +/-   ##
==========================================
- Coverage   97.82%   97.81%   -0.01%     
==========================================
  Files         588      588              
  Lines       66422    66239     -183     
==========================================
- Hits        64976    64793     -183     
  Misses       1446     1446              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@isPANN
Copy link
Collaborator Author

isPANN commented Mar 25, 2026

Finding: ILP solver hangs due to unbounded variable domains

During this PR, we discovered that ILP<i32> reductions can cause HiGHS to hang indefinitely, even on tiny instances (e.g., 4 vertices / 23 variables).

Root cause

The ILP solver sets variable upper bounds from V::DIMS_PER_VAR:

v = v.max((V::DIMS_PER_VAR - 1) as f64);  // i32 → 2,147,483,647

For ILP<i32>, every variable gets domain [0, 2^31]. Even though constraints like x_{v,p} <= 1 or p_v <= n-1 logically bound variables to small ranges, HiGHS receives the full [0, 2^31] domain and its branch-and-bound becomes extremely slow.

This particularly affects auxiliary variables that lack explicit single-variable <= C constraints:

  • z variables in minimax formulations (OLA z_e, Sequencing z)
  • position/order variables only bounded via linking constraints (p_v = Σ p·x_{v,p})

Fix (3 parts)

  1. ILP solver (src/solvers/ilp/solver.rs): Before passing to HiGHS, scan constraints for single-variable x_i <= C patterns and use the tighter bound instead of DIMS_PER_VAR
  2. OLA ILP: Added z_e <= n-1 (max position difference)
  3. Sequencing ILP: Added z <= Σ|costs| (max cumulative cost)

Impact

Before fix: model_specs_are_optimal hangs (OLA canonical example triggers OLA→ILP reduction path, HiGHS never returns).
After fix: all example-db tests pass in ~2 seconds.

Recommendation for future ILP rules

Always add explicit var <= upper_bound constraints for non-binary variables. This ensures HiGHS gets tight bounds regardless of DIMS_PER_VAR. See #772 for remaining hardcoded examples that should be converted.

…verage

- Remove #[ignore] from OLA ILP tests (HiGHS is fast now with z bounds)
- Add rural_postman tests: wrong config length, is_weighted, solver aggregate
- Add SWCP test: weight bound exceeded
- Covers codecov gaps in rural_postman, shortest_weight_constrained_path,
  and optimallineararrangement_ilp

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR upgrades a large set of models that were previously encoded as decision problems (Value = Or + threshold) into true optimization models (Value = Min/Max<T>), and updates the associated ILP reductions, CLI creation/help text, canonical examples, and tests so that solvers produce and validate optimal objective values.

Changes:

  • Convert 17 models from thresholded decision formulations to optimization (Min/Max) and update evaluate() to return objective values (Some(v) / None).
  • Rewrite multiple → ILP reductions from feasibility to optimization by removing bound constraints and adding objective functions (including minimax encodings where needed).
  • Update CLI help/tests/docs and unit/integration tests to reflect new constructors, config encodings (padding/slots), and optimization semantics.

Reviewed changes

Copilot reviewed 79 out of 79 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/suites/integration.rs Updates integration assertions to check Value presence for optimization models.
src/unit_tests/trait_consistency.rs Updates constructors in trait consistency tests after removing threshold params.
src/unit_tests/solvers/customized/solver.rs Adjusts customized-solver tests for new optimization semantics.
src/unit_tests/rules/sumofsquarespartition_ilp.rs Updates SSP ILP tests to compare optimal values (BF vs ILP).
src/unit_tests/rules/stackercrane_ilp.rs Switches round-trip helper to optimization variant; updates constructor.
src/unit_tests/rules/shortestweightconstrainedpath_ilp.rs Updates SWCP ILP tests to optimization objective and Min values.
src/unit_tests/rules/shortestcommonsupersequence_ilp.rs Updates SCS ILP tests for padding-based optimization objective.
src/unit_tests/rules/sequencingtominimizemaximumcumulativecost_ilp.rs Updates STMMCC ILP tests to compare optimal objective values.
src/unit_tests/rules/ruralpostman_ilp.rs Updates RPP ILP tests for optimization and adds BF-vs-ILP optimality check.
src/unit_tests/rules/optimallineararrangement_ilp.rs Updates OLA ILP tests to optimization and adds BF-vs-ILP optimality check.
src/unit_tests/rules/multiplecopyfileallocation_ilp.rs Updates MCFA ILP tests to optimization and removes budget constraint checks.
src/unit_tests/rules/mixedchinesepostman_ilp.rs Updates MCPP ILP tests to optimization; adds BF-vs-ILP comparisons.
src/unit_tests/rules/minmaxmulticenter_ilp.rs Updates MMC ILP tests to ILP<i32> minimax + objective variable z.
src/unit_tests/rules/minimumcutintoboundedsets_ilp.rs Updates MinCutBS ILP tests to optimization round-trip helper and Some checks.
src/unit_tests/rules/longestcommonsubsequence_ilp.rs Updates LCS ILP tests for padding encoding and BF-vs-ILP optimality checks.
src/unit_tests/rules/longestcircuit_ilp.rs Updates LongestCircuit ILP tests to maximization objective and optimization helper.
src/unit_tests/rules/lengthboundeddisjointpaths_ilp.rs Updates LBDP ILP tests to optimization round-trip helper (maximize #paths).
src/unit_tests/rules/expectedretrievalcost_ilp.rs Updates ERC ILP tests to objective-based optimization and cost comparisons.
src/unit_tests/rules/capacityassignment_ilp.rs Updates CA ILP tests to optimize cost subject to delay budget.
src/unit_tests/models/misc/sequencing_to_minimize_maximum_cumulative_cost.rs Updates STMMCC model tests to Min(Some(v))/Min(None) semantics.
src/unit_tests/models/misc/expected_retrieval_cost.rs Updates ERC model tests to return objective values (no threshold).
src/unit_tests/models/misc/capacity_assignment.rs Updates CA model tests to Min<u128> objective and feasibility via delay budget.
src/unit_tests/models/graph/shortest_weight_constrained_path.rs Updates SWCP model tests to Min objective and witness optimality assertions.
src/unit_tests/models/graph/multiple_copy_file_allocation.rs Updates MCFA model tests to optimization objective and removes threshold logic.
src/unit_tests/models/graph/mixed_chinese_postman.rs Updates MCPP model tests for optimization objective values.
src/unit_tests/models/graph/longest_circuit.rs Updates LongestCircuit model tests for maximization objective values.
src/unit_tests/example_db.rs Updates example-db assertions to reflect removed bound fields / new optimal values.
src/solvers/ilp/solver.rs Tightens per-variable upper bounds using single-var <= constraints to improve ILP performance.
src/solvers/customized/solver.rs Updates MinimumCardinalityKey customized backend to remove bound-based pruning.
src/rules/sumofsquarespartition_ilp.rs Converts SSP ILP reduction from feasibility to minimization objective.
src/rules/stackercrane_ilp.rs Converts StackerCrane ILP reduction from bound constraint to objective minimization.
src/rules/shortestweightconstrainedpath_ilp.rs Converts SWCP ILP reduction to minimize length subject to weight constraint.
src/rules/shortestcommonsupersequence_ilp.rs Implements padding + contiguous-padding constraints and objective for SCS minimization via maximizing padding.
src/rules/sequencingtominimizemaximumcumulativecost_ilp.rs Converts STMMCC reduction to minimax ILP with explicit z objective variable.
src/rules/ruralpostman_ilp.rs Converts RPP reduction from length-bound feasibility to objective minimization.
src/rules/optimallineararrangement_ilp.rs Converts OLA reduction from bound feasibility to objective minimization.
src/rules/multiplecopyfileallocation_ilp.rs Converts MCFA reduction to objective minimization and redefines Big-M for unreachable pairs.
src/rules/mixedchinesepostman_ilp.rs Converts MCPP reduction from bound feasibility to objective minimization.
src/rules/minmaxmulticenter_ilp.rs Converts MMC reduction to minimax ILP<i32> with binary bounds + z objective.
src/rules/minimumcutintoboundedsets_ilp.rs Converts MinCutBS reduction to objective minimization (removes cut bound).
src/rules/longestcircuit_ilp.rs Converts LongestCircuit ILP reduction to maximization objective (removes length threshold).
src/rules/lengthboundeddisjointpaths_ilp.rs Redesigns LBDP ILP reduction to maximize number of active path slots with activation vars.
src/rules/expectedretrievalcost_ilp.rs Converts ERC reduction from bound feasibility to objective minimization.
src/rules/capacityassignment_ilp.rs Converts CA reduction to objective minimization (cost) subject to delay constraint.
src/models/set/minimum_cardinality_key.rs Converts MinimumCardinalityKey to Min<i64> objective and removes bound from schema/struct.
src/models/misc/sum_of_squares_partition.rs Converts SSP model to Min<i64> objective and removes bound field/serialization.
src/models/misc/stacker_crane.rs Converts StackerCrane model to Min<i32> objective and removes bound.
src/models/misc/sequencing_to_minimize_maximum_cumulative_cost.rs Converts STMMCC model to Min<i64> objective and removes bound.
src/models/misc/mod.rs Updates misc model list entry for StackerCrane to reflect optimization semantics.
src/models/misc/longest_common_subsequence.rs Converts LCS to padding-based fixed-length encoding and Max<usize> objective.
src/models/misc/expected_retrieval_cost.rs Converts ERC to Min<f64> objective and removes bound.
src/models/misc/capacity_assignment.rs Converts CA to Min<u128> objective and removes cost budget.
src/models/graph/optimal_linear_arrangement.rs Converts OLA to Min<usize> objective and removes bound.
src/models/graph/multiple_copy_file_allocation.rs Converts MCFA to Min<i64> objective and removes bound.
src/models/graph/mixed_chinese_postman.rs Converts MCPP to Min<W::Sum> objective and removes bound.
src/models/graph/minimum_cut_into_bounded_sets.rs Converts MinCutBS to Min<W::Sum> objective and removes cut bound.
src/models/graph/min_max_multicenter.rs Converts MMC to Min<W::Sum> objective and removes bound.
src/models/graph/longest_circuit.rs Converts LongestCircuit to Max<W::Sum> objective and removes bound.
problemreductions-cli/src/mcp/tests.rs Updates MCP test payloads to reflect removed bound parameter(s).
problemreductions-cli/src/cli.rs Updates CLI “flags by problem type” + args docs for removed parameters.
docs/src/cli.md Updates docs output/examples for renamed fields and updated parameterization.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 80 to 84
// Search for any minimal key (the smallest one will be found first due to
// branch-and-bound ordering in the FD subset search)
let result = fd_subset_search::search_fd_subset(
n,
&essential,
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

solve_minimum_cardinality_key currently searches for any minimal key (subset-minimal superkey) and returns the first one found. search_fd_subset does DFS that tries including each attribute first, so the first minimal key found is biased toward larger keys and is not guaranteed to be the minimum-cardinality key required by the optimization objective. Please change the search strategy to guarantee minimum size (e.g., iterative deepening by cardinality with pruning, or change the DFS order to explore smaller subsets first while maintaining minimality checks), and add an assertion in tests that the customized solver’s returned witness matches the brute-force optimum cardinality/value.

Copilot uses AI. Check for mistakes.
MinimumCutIntoBoundedSets --graph, --edge-weights, --source, --sink, --size-bound, --cut-bound
MinimumCutIntoBoundedSets --graph, --edge-weights, --source, --sink, --size-bound
HamiltonianCircuit, HC --graph
LongestCircuit --graph, --edge-weights, --bound
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI help still lists LongestCircuit as requiring --bound, but the model (and create implementation) has been upgraded to optimization and no longer has a bound parameter. Update the flags-by-problem-type list accordingly so users aren’t instructed to pass an unused/invalid flag.

Suggested change
LongestCircuit --graph, --edge-weights, --bound
LongestCircuit --graph, --edge-weights

Copilot uses AI. Check for mistakes.
#[arg(long)]
pub required_edges: Option<String>,
/// Bound parameter (lower bound for LongestCircuit; upper or length bound for BoundedComponentSpanningForest, GroupingBySwapping, LengthBoundedDisjointPaths, LongestCommonSubsequence, MultipleCopyFileAllocation, MultipleChoiceBranching, OptimalLinearArrangement, RootedTreeArrangement, RuralPostman, ShortestCommonSupersequence, or StringToStringCorrection)
/// Bound parameter (lower bound for LongestCircuit; upper or length bound for BoundedComponentSpanningForest, GroupingBySwapping, LengthBoundedDisjointPaths, LongestCommonSubsequence, MultipleCopyFileAllocation, MultipleChoiceBranching, RootedTreeArrangement, RuralPostman, or StringToStringCorrection)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --bound flag docstring still mentions models that no longer take a bound (e.g., LongestCircuit, LongestCommonSubsequence, MultipleCopyFileAllocation). This makes pred create help misleading after the optimization upgrade; please remove those models from this description (and consider deleting the flag entirely if nothing uses it anymore).

Suggested change
/// Bound parameter (lower bound for LongestCircuit; upper or length bound for BoundedComponentSpanningForest, GroupingBySwapping, LengthBoundedDisjointPaths, LongestCommonSubsequence, MultipleCopyFileAllocation, MultipleChoiceBranching, RootedTreeArrangement, RuralPostman, or StringToStringCorrection)
/// Bound parameter (upper or length bound for BoundedComponentSpanningForest, GroupingBySwapping, LengthBoundedDisjointPaths, MultipleChoiceBranching, RootedTreeArrangement, RuralPostman, or StringToStringCorrection)

Copilot uses AI. Check for mistakes.
docs/src/cli.md Outdated
pred create MaxCut --graph 0-1,1-2,2-0 -o maxcut.json
pred create MinMaxMulticenter --graph 0-1,1-2,2-3 --weights 1,1,1,1 --edge-weights 1,1,1 --k 2 --bound 1 -o pcenter.json
pred create MinMaxMulticenter --graph 0-1,1-2,2-3 --weights 1,1,1,1 --edge-weights 1,1,1 --k 2 -o pcenter.json
pred create ShortestWeightConstrainedPath --graph 0-1,0-2,1-3,2-3,2-4,3-5,4-5,1-4 --edge-lengths 2,4,3,1,5,4,2,6 --edge-weights 5,1,2,3,2,3,1,1 --source-vertex 0 --target-vertex 5 --length-bound 10 --weight-bound 8 -o swcp.json
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SWCP example still uses --length-bound, but the CLI flags list above (and the model upgrade) removed that parameter. This example should be updated to remove --length-bound (and keep only --weight-bound) so it remains runnable.

Suggested change
pred create ShortestWeightConstrainedPath --graph 0-1,0-2,1-3,2-3,2-4,3-5,4-5,1-4 --edge-lengths 2,4,3,1,5,4,2,6 --edge-weights 5,1,2,3,2,3,1,1 --source-vertex 0 --target-vertex 5 --length-bound 10 --weight-bound 8 -o swcp.json
pred create ShortestWeightConstrainedPath --graph 0-1,0-2,1-3,2-3,2-4,3-5,4-5,1-4 --edge-lengths 2,4,3,1,5,4,2,6 --edge-weights 5,1,2,3,2,3,1,1 --source-vertex 0 --target-vertex 5 --weight-bound 8 -o swcp.json

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +64
pub fn new(alphabet_size: usize, strings: Vec<Vec<usize>>) -> Self {
let max_length = strings.iter().map(|s| s.len()).min().unwrap_or(0);
assert!(
max_length >= 1 || strings.is_empty(),
"at least one string must be non-empty"
);
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LongestCommonSubsequence::new computes max_length as the minimum string length, but then asserts that at least one string must be non-empty. This panics for valid instances where any input string is empty (the correct optimum is length 0), and also for the case where all strings are empty. Please allow max_length == 0 and handle it consistently (e.g., allow an empty config/dims, or special-case to a 0-length padded encoding) instead of panicking.

Copilot uses AI. Check for mistakes.
@zazabap zazabap moved this to Review pool in Good issues Mar 25, 2026
@zazabap zazabap moved this from Review pool to Under review in Good issues Mar 25, 2026
@zazabap
Copy link
Collaborator

zazabap commented Mar 25, 2026

Agentic Review Report

Structural Check

Structural Review: Generic (17 Model Upgrades + ILP Rule Rewrites)

Build Status

  • make test: PASS
  • make clippy: PASS

Blacklisted Files

  • PASS — none of the four auto-generated files appear in the diff.

Semantic Review — Models Sampled (6)

Model evaluate() dims() Threshold removed declare_variants!
OptimalLinearArrangement OK — Min(Some(cost)) for valid perms, Min(None) otherwise OK — vec![n; n] OK OK
LongestCircuit OK — Max(Some(total_length)) for valid circuits OK — vec![2; num_edges] OK OK
LongestCommonSubsequence OK — padding validation correct OK — vec![alphabet_size+1; max_length] OK OK
ShortestWeightConstrainedPath OK — weight bound as constraint, length as objective OK — vec![2; num_edges] OK — length_bound removed, weight_bound kept OK
MinMaxMulticenter OK — multi-source Dijkstra for max distance OK — vec![2; num_vertices] OK — bound removed, k kept OK
LengthBoundedDisjointPaths OK — counts valid non-empty path slots OK — vec![2; max_paths * num_vertices] OK — num_paths_required removed, max_length kept OK

Semantic Review — ILP Rules Sampled (4)

Rule Objective extract_solution Overhead
OLA → ILP OK — minimize sum z_e OK ISSUEnum_constraints under-counts by num_edges (missing z_e <= n-1 upper bounds)
LongestCircuit → ILP OK — maximize sum l_e * y_e OK ISSUEnum_vars over-counts by 4 * num_edges; num_constraints over-counts by num_vertices + 2 * num_edges (n-1 vs n commodity-count mismatch)
LCS → ILP OK — maximize non-padding positions OK OK
SWCP → ILP OK — minimize total path length OK OK

Mathematical Correctness

  • OLA trace (n=4 path): bijection assignment + abs-value linearization correct.
  • SWCP trace (3-vertex path): arc formulation + MTZ subtour elimination correct.
  • LCS trace (3-symbol strings): subsequence matching + padding contiguity correct.

Issue Compliance (#765)

All 17 models correctly upgraded:

  • Group A (12): LongestCircuit ✓, OptimalLinearArrangement ✓, RuralPostman ✓, MixedChinesePostman ✓, StackerCrane ✓, MinimumCardinalityKey ✓, SequencingToMinimizeMaximumCumulativeCost ✓, MultipleCopyFileAllocation ✓, ExpectedRetrievalCost ✓, SumOfSquaresPartition ✓, LongestCommonSubsequence ✓, ShortestCommonSupersequence ✓
  • Group B (5): MinimumCutIntoBoundedSets ✓, ShortestWeightConstrainedPath ✓, CapacityAssignment ✓, MinMaxMulticenter ✓, LengthBoundedDisjointPaths ✓
  • All threshold fields removed, all constraint parameters retained.
  • All 7 existing ILP rules rewritten + 9 new ILP rules added.

Issues Found

  1. OLA→ILP overhead num_constraints under-counts by num_edges (src/rules/optimallineararrangement_ilp.rs:52)
  2. LongestCircuit→ILP overhead num_vars over-counts by 4 * num_edges (src/rules/longestcircuit_ilp.rs:45)
  3. LongestCircuit→ILP overhead num_constraints over-counts by num_vertices + 2 * num_edges (src/rules/longestcircuit_ilp.rs:46)

Quality Check

Quality Review

Design Principles

  • DRY: ISSUE (minor) — Padding validation logic in LongestCommonSubsequence::evaluate (src/models/misc/longest_common_subsequence.rs:145-175) and ShortestCommonSupersequence::evaluate (src/models/misc/shortest_common_supersequence.rs:136-168) follows an identical 6-step pattern. Could extract a shared validate_padded_config helper.
  • KISS: OK — Model upgrades are clean. Padding encoding is straightforward. ILP solver's upper_bounds tightening for ILP<i32> is sensible.
  • HC/LC: OK — Each model is self-contained. ILP rule files cleanly separate reduction from model. No mixed concerns.

HCI (CLI changed)

  • Error messages: ISSUE (important) — Several CLI usage strings still reference --bound for models that no longer accept it:
    • problemreductions-cli/src/cli.rs:236 — LongestCircuit help still shows --bound
    • problemreductions-cli/src/cli.rs:283-285 — MixedChinesePostman, RuralPostman, StackerCrane help still shows --bound
    • problemreductions-cli/src/commands/create.rs:579,649,652,655,1837,1844,1872,3817 — example strings still show --bound for these models
    • Users following these examples will pass --bound that is silently ignored.
  • Discoverability: OK — Updated descriptions properly reflect optimization semantics.
  • Consistency: ISSUE (important) — Some models had --bound removed from help text (OLA, MinMaxMulticenter, SumOfSquaresPartition, CapacityAssignment) while others (LongestCircuit, MixedChinesePostman, RuralPostman, StackerCrane) were missed.
  • Least surprise: ISSUE — Silently-ignored --bound flags from stale usage examples.
  • Feedback: OK

Test Quality

  • LCS tests: Good coverage of padding validation, subsequence checking, brute-force optimality. Minor issue: test_lcs_bruteforce_finds_optimum uses weak assertion Max(Some(v)) if v >= 1 instead of assert_eq!(value, Max(Some(2))).
  • RuralPostman tests: Good coverage. Properly checks Min(Some(cost)).
  • MinMaxMulticenter ILP tests: Good closed-loop BF vs ILP comparison.
  • LCS ILP tests: Good closed-loop testing. Tests all-padding case.
  • CustomizedSolver tests: Changed to remove bound from MinimumCardinalityKey but does not verify optimality.

Issues

Critical (Must Fix)

  1. CustomizedSolver for MinimumCardinalityKey may return suboptimal witnesses (src/solvers/customized/solver.rs:69-98). The DFS search_fd_subset tries "include attribute first" before "exclude", tending to find keys with MORE attributes. Since MinimumCardinalityKey now uses Min<i64> (optimization), the customized solver must return an optimal witness. The comment at line 80-81 ("the smallest one will be found first due to branch-and-bound ordering") appears incorrect — the search order favors larger keys.

Important (Should Fix)

  1. Stale CLI usage strings and help text still reference --bound for LongestCircuit, MixedChinesePostman, RuralPostman, StackerCrane across cli.rs and create.rs.

Minor (Nice to Have)

  1. DRY: Duplicated padding validation in LCS and SCS evaluate().
  2. Weak assertion: LCS brute-force test should check exact optimal value Max(Some(2)) instead of >= 1.

Agentic Feature Tests

Test Environment

  • Build: succeeded (8 pre-existing warnings, none from this PR)

Results

Model list show create solve reduce path
LongestCircuit PASS PASS PASS PASS (Max(18)) PASS PASS
OptimalLinearArrangement PASS PASS PASS PASS (Min(11)) PASS FAIL*
MinimumCardinalityKey PASS PASS PASS PASS (Min(2)) N/A N/A
ShortestCommonSupersequence PASS PASS PASS PASS (Min(3)) PASS PASS
MinMaxMulticenter PASS PASS PASS PASS (Min(1)) N/A FAIL*

* Pre-existing issue: pred path X ILP fails when reduction targets ILP/i32 because bare ILP resolves to default ILP/bool. Not a regression from this PR.

Details

  • All 5 tested models correctly display optimization value types (Max/Min instead of Or)
  • ILP and brute-force solvers return consistent results for all tested models
  • Example creation works for all tested models
  • No regressions found from this PR

Issues Found

# Issue Severity Classification
1 pred path <Name> ILP fails for models reducing to ILP/i32 (not ILP/bool) minor confirmed, pre-existing

Generated by review-pipeline

@zazabap zazabap moved this from Under review to Final review in Good issues Mar 25, 2026
- Fix CustomizedSolver for MinimumCardinalityKey to guarantee minimum
  cardinality using iterative deepening by subset size, not just any
  minimal key (critical correctness fix for optimization upgrade)
- Remove stale --bound references from CLI help and usage strings for
  LongestCircuit, MixedChinesePostman, RuralPostman, StackerCrane,
  LCS, SCS, MinimumCardinalityKey, MultipleCopyFileAllocation, and
  SequencingToMinimizeMaximumCumulativeCost
- Remove stale --num-paths-required from LengthBoundedDisjointPaths help
- Remove stale --k from MinimumCardinalityKey help
- Remove --length-bound from SWCP example in docs/src/cli.md
- Fix OLA->ILP overhead num_constraints (add num_edges for z_e bounds)
- Fix LongestCircuit->ILP overhead (n-1 commodities, not n)
- Allow LCS max_length == 0 for empty input strings instead of panicking
- Strengthen LCS brute-force test assertion to check exact optimal value
- Add optimality assertions to CustomizedSolver MCK tests
- Update cli_tests to reflect removed --bound flags

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@zazabap
Copy link
Collaborator

zazabap commented Mar 25, 2026

Fix Summary (cd58bdf)

Addresses all review comments and agentic review findings from the previous round.

Review Comments Addressed

# Comment Fix
1 CustomizedSolver for MinimumCardinalityKey returns suboptimal witnesses (src/solvers/customized/solver.rs:84) — DFS search order biased toward larger keys Rewrote solve_minimum_cardinality_key to use iterative deepening by cardinality. The search now tries the smallest possible key size first and increments until a minimal key is found, guaranteeing the returned witness has minimum cardinality. Added two optimality tests comparing against brute-force.
2 CLI help lists --bound for LongestCircuit (cli.rs:236) Removed --bound from LongestCircuit help entry
3 --bound docstring lists removed models (cli.rs:556) Updated docstring to only list models that still use --bound (BoundedComponentSpanningForest, GroupingBySwapping, LengthBoundedDisjointPaths, MultipleChoiceBranching, RootedTreeArrangement, StringToStringCorrection)
4 SWCP example uses removed --length-bound (docs/src/cli.md:357) Removed --length-bound 10 from the example
5 LCS panics on max_length == 0 (src/models/misc/longest_common_subsequence.rs:64) Removed the max_length >= 1 assertion. Empty strings now produce max_length = 0 with an empty config space, and evaluate(&[]) returns Max(Some(0)). Added two tests for empty-string edge cases.

Agentic Review Findings Addressed

# Finding Fix
1 OLA→ILP overhead num_constraints under-counts by num_edges — missing z_e <= n-1 upper bounds Changed 2 * num_edges3 * num_edges in overhead expression (src/rules/optimallineararrangement_ilp.rs:52)
2 LongestCircuit→ILP overhead num_vars over-counts — flow uses n-1 commodities, not n Changed 3 * num_edges + num_vertices + 2 * num_edges * num_verticesnum_edges + num_vertices + 2 * num_edges * (num_vertices - 1)
3 LongestCircuit→ILP overhead num_constraints over-counts — same n-1 vs n mismatch Changed num_vertices + 1 + num_vertices^2 + 2 * num_edges * num_vertices1 + num_vertices^2 + 2 * num_edges * (num_vertices - 1)
4 Stale --bound in CLI help/usage for 8+ models Removed --bound from help text and example_for strings for: LongestCircuit, MixedChinesePostman, RuralPostman, StackerCrane, LCS, SCS, MinimumCardinalityKey (--k), MultipleCopyFileAllocation, SequencingToMinimizeMaximumCumulativeCost. Also removed stale --num-paths-required from LengthBoundedDisjointPaths.
5 Weak LCS test assertionassert!(matches!(value, Max(Some(v)) if v >= 1)) Strengthened to assert_eq!(value, Max(Some(2)))
6 pred path X ILP fails for ILP/i32 targets Pre-existing issue, not a regression from this PR — no fix needed

Not Addressed (deferred)

# Finding Reason
1 DRY: Duplicated padding validation in LCS/SCS evaluate() Minor quality issue. Only two instances, logic is straightforward. Can be extracted in a follow-up if desired.

Verification

  • make check (fmt + clippy + test): ✅ all pass
  • Updated cli_tests.rs to reflect removed --bound flags (StackerCrane, SequencingToMinMCC)

Copy link
Collaborator

@zazabap zazabap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Collaborator

@zazabap zazabap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed: all review comments addressed, make check passes, fixes verified.

@zazabap zazabap merged commit 66e9050 into main Mar 25, 2026
5 checks passed
@zazabap zazabap moved this from Final review to Done in Good issues Mar 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Upgrade 17 decision models to optimization versions

3 participants